package com.redshark.wmm.Bluetooth;

import java.io.*;

import com.redshark.wmm.MeasurementRequestor;
import com.redshark.wmm.Bluetooth.ScopeMessage.ScopeMessageException;

import android.util.Log;

public abstract class BluetoothListener implements Runnable {

	private static final String TAG = "BTListener";

	// Acknowledge Constants
	@SuppressWarnings("unused")
	private static final int ACK_OK = 0;
	@SuppressWarnings("unused")
	private static final int ACK_INVALID_COMMAND = 1;
	@SuppressWarnings("unused")
	private static final int ACK_BAD_CHECKSUM = 2;
	@SuppressWarnings("unused")
	private static final int ACK_INCOMPLETE_MSG = 3;
	private static final int ACK_BEGIN_DATA = 4;
	private static final int ACK_END_DATA = 5;

	@SuppressWarnings("unused")
	private static final int ACK_INVALID_CFG = 0x64;

	// Scope
	private int scopeMode;
	public static final int SCOPE_DISABLED = 0;
	public static final int SCOPE_PROCESSING = 1;
	public static final int SCOPE_DONE = 2;

	// Frequency
	long lastTime = 0;
	long lastFreqCount = 0;

	private boolean shutdown;

	public int getScopeMode() {
		return scopeMode;
	}

	public void setScopeMode(int scopeMode) {
		this.scopeMode = scopeMode;
	}

	private InputStream inputStream;

	public BluetoothListener(InputStream inputStream) {
		this.inputStream = inputStream;
		shutdown = false;
	}

	public abstract void onSDCPMessage(SDCPMessage m);

	public abstract void onScopeMessage(ScopeMessage m);

	private static int[] UnpackData(byte[] packagedData, int numData) {
		int[] data = new int[numData];
		int j = 0;

		for (int i = 0; i < numData; i++) {
			data[i] = (packagedData[j++] & 0xFF) << 8;
			data[i] = data[i] | (0xFF & packagedData[j++]);
		}

		return data;
	}

	public void run() {
		while (!shutdown) {
			try {
				BufferedInputStream in = new BufferedInputStream(inputStream,
						8192);

				SDCPMessage message = processRequest(in);

				if (message != null) {
					// Check to see if this happens to be a scope mode packet.
					if (message.isAck()
							&& message.getAck() == ACK_BEGIN_DATA
							&& (message.getCommand() >= MeasurementRequestor.SCOPE_1 && message
									.getCommand() <= MeasurementRequestor.CURRENT_SCOPE_HIGH)) {
						ScopeMessage scopeMessage;

						// Make up the scope message
						scopeMessage = new ScopeMessage(message.getType(),
								message.getCommand(), message.getMessageNum());

						// Run scope mode to get packets
						scopeMessage = runScope(scopeMessage, in);

						// Process Closing bracket
						SDCPMessage closingMessage = processRequest(in);

						if (closingMessage.getMessageNum() == message
								.getMessageNum()
								&& closingMessage.isAck()
								&& closingMessage.getAck() == ACK_END_DATA) {
							onScopeMessage(scopeMessage);
						} else {
							Log.e(TAG, "Unexpected/Malformed Scope Packet");
						}
					}
					// It is a normal packet
					else {
						onSDCPMessage(message);
					}
				}
			} catch (IOException e) {
				// The socket must be closed before the blocking I/O will
				// terminate
				// Shutdown the thread whenever we hit an IO exception
				Log.d(TAG, e.toString());
				shutdown = true;
			}
		}
	}

	private SDCPMessage processRequest(BufferedInputStream in)
			throws IOException {
		try {
			// Holds First part and last part of variable length message
			byte[] bytes0_3 = new byte[3];
			byte[] otherBytes;

			// Holds parsed out data
			int type;
			int command;
			int messageNum;
			int length;
			int checkSum;
			int ack = 0;
			boolean ackPresent;

			int[] data = null;

			int checkedSum = 0x00; // Prefil for xor operation

			for (int i = 0; i < 3; i++) {
				while (in.available() < 1 && !shutdown)
					;
				if (shutdown) {
					return null;
				}
				bytes0_3[i] = (byte) in.read();
			}

			if (bytes0_3[0] != '[') {
				throw new MessageParseException("Missing Starting Bracket "
						+ Byte.toString(bytes0_3[0]) + " "
						+ Byte.toString(bytes0_3[1]) + " "
						+ Byte.toString(bytes0_3[2]));
			}

			// Grab length
			length = bytes0_3[2] & 0x1F;

			// If length is 0 then we are going to get an ack code so there is
			// really one data byte still
			if (length == 0) {
				length = 1;
				ackPresent = true;
			} else {
				ackPresent = false;
			}

			// Get the rest of the bytes
			otherBytes = new byte[length + 2];

			for (int i = 0; i < length + 2; i++) {
				while (in.available() < 1 && !shutdown)
					;
				if (shutdown) {
					return null;
				}
				otherBytes[i] = (byte) in.read();
			}

			// Check checksum

			// Calculate
			checkedSum = bytes0_3[1] ^ bytes0_3[2];
			for (int i = 0; i < length; i++) {
				checkedSum = checkedSum ^ otherBytes[i];
			}

			// Grab from data and compare
			checkSum = otherBytes[length];
			if (checkSum != checkedSum) {
				throw new MessageParseException("Failed Checksum");
			}

			// Get & Check Type
			type = bytes0_3[1] & 0x80; // 0b10000000
			type = type >> 7;

			if (type != 1) {
				throw new MessageParseException("Packet not response");
			}

			// Get & Check Message Number
			messageNum = bytes0_3[2] & 0xE0;
			messageNum = messageNum >> 5;

			// Get & Check Command field
			command = bytes0_3[1] & 0x7F;

			Log.v(TAG, "Recieved Message" + messageNum + " Command: " + command);

			// Parse Data or ACK
			if (!ackPresent) {
				data = UnpackData(otherBytes, length / 2);
			} else {
				ack = otherBytes[0];
			}

			// Check for closing bracket
			if (otherBytes[length + 1] != ']') {
				throw new MessageParseException("Missing Closing Bracket");
			}

			// OK, now we are going to do something with our data

			// If not, package 'er up
			if (ackPresent) {
				return new SDCPMessage(type, command, messageNum, ack);
			} else {
				return new SDCPMessage(type, command, messageNum, data);
			}

		} catch (MessageParseException e) {
			Log.d(TAG, "Bad Packet: " + e.getMessage());
			if (!shutdown) {
				if (scopeMode != SCOPE_PROCESSING) {
					StringBuilder sb = new StringBuilder();
					byte b = (byte) in.read();
					int i = 0;
					while (b != ']' && !shutdown) {
						if (shutdown) {
							return null;
						}
						b = (byte) in.read();
						sb.append(String.format("%8s",
								Integer.toBinaryString(b & 0xFF)).replace(' ',
								'0'));
						sb.append(" ");
						i++;
					}
					Log.d(TAG, "Read " + i + " bytes: " + sb.toString()
							+ " to realign stream");
				}
			}
		}
		// Obligatory return since it doesnt think I have to return
		return null;

	}

	private ScopeMessage runScope(ScopeMessage message, BufferedInputStream in)
			throws IOException {
		int count = 0;
		while (count < 256) { // Run till we have 255 data points or we timeout
								// //It takes about 50.4ms to send the data
			// TODO: Add timeout
			byte[] scopeData = new byte[2];
			byte b;
			b = (byte) in.read();

			if (((b & 0x80) >> 8) == 0) {
				scopeData[0] = b;
				scopeData[1] = (byte) in.read();

				// Work with our data
				int data;

				// Grab our two bit messagenum
				int msgNum1 = (scopeData[0] & 0x60) >> 5;
				int msgNum2 = (scopeData[1] & 0x60) >> 5;

				// Pull our data out
				int upper5Data = scopeData[0] & 0x1F;
				int lower5Data = scopeData[1] & 0x1F;

				// If the messageNumbs are the same, smerge them together and do
				// something
				if (msgNum1 == msgNum2) {
					data = upper5Data << 5;
					data = data | lower5Data;

					try {
						message.addData(data);
					} catch (ScopeMessageException e) {
						e.printStackTrace();
						Log.e(TAG, e.getMessage());
					}
					if (count > 240) {
						Log.v(TAG, "Scope Message " + count + " received!");
					}

				} else {
					Log.v(TAG, "Scope Message Num " + msgNum1 + " and "
							+ msgNum2 + " dont match.");
					// Advance 1
					in.read();
				}
			} else {
				Log.v(TAG, "Data byte doesnt begin with expected 1");
			}
			count++;
		}
		return message;
	}

	public void shutdown() {
		this.shutdown = true;
	}

	public class MessageParseException extends Exception {

		/**
		 * 
		 */
		private static final long serialVersionUID = 1689463994657736725L;

		public MessageParseException(String message) {
			super(message);
		}

		public MessageParseException(String message, Throwable throwable) {
			super(message, throwable);
		}

	}
}